/*
 * =====================================================================================
 *       Filename:  executer.c
 *    Description:  命令执行代码
 *        Created:  2017-17-05 13:51
 *         Author:  mien, m@luym.cn
 * =====================================================================================
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include "executer.h"
#include "global.h"

int exfd[2];
pid_t expid;

int childProcess()
{
    int pfd[2];

    if (pipe(pfd) < 0) {
        return -1;
    }

    switch (fork()) {
    case -1:
        return -1;
    case 0:
        close(pfd[1]);

        if (pfd[0] != STDIN_FILENO) {
            dup2(pfd[0], STDIN_FILENO);
            close(pfd[0]);
        }

        int i = 0;
        char c;
        char path[EX_BUF_SIZE];

        while (read(STDIN_FILENO, &c, 1) > 0) {
            path[i++] = c;
            if (c == '\0') {
                break;
            }
            if (i == EX_BUF_SIZE) {
                path[--i] = '\0';
                break;
            }
        }

        // 切换工作目录为文件所在目录
        char* slash = strrchr(path, '/');
        if (slash) {
            *slash = '\0';
            chdir(path);
            *slash = '/';
        }

        DEBUG(("program to be executed is '%s'\n", path))

        // 执行
        extern char** environ;
        execle(path, path, NULL, environ);

        // 出错
        perror("execle");
        _exit(EXIT_FAILURE);
    }

    // executer
    close(pfd[0]);
    return pfd[1];
}

void initExecuter()
{
    char exbuf[EX_BUF_SIZE];

    if (pipe(exfd) == -1) {
        perror("pipe");
        exit(EXIT_FAILURE);
    }

    expid = fork();

    if (expid == -1) {
        perror("fork");
        exit(EXIT_FAILURE);
    }

    if (expid == 0) {
        // 子进程, executer, 用于等待执行命令

        // 不关心下一步 vfork 出来的子进程的运行
        /* (void) signal(SIGCHLD, SIG_IGN); */

        // 关闭管道写端
        close(exfd[1]);

        // 从管道读取命令数据(无数据时阻塞)
        int n, expect, len, sequence;
        int child_fd = -1;

        while (1) {

            // 读取后面的数据长度到 len
            n = read(exfd[0], &len, sizeof(int));

            if (n <= 0) {
                break;
            }

            if (len > 0) {

                if (child_fd == -1) {
                    child_fd = childProcess();
                    sequence = 0;
                }

                expect = len;

                while (expect > 0) {
                    n = read(exfd[0], exbuf, (expect > EX_BUF_SIZE) ? EX_BUF_SIZE :  expect);
                    if (n > 0) {
                        if (child_fd > 0) {
                            write(child_fd, exbuf, n);
                        }
                        expect -= n;
                    }
                    if (n <= 0) {
                        break;
                    }
                }

                sequence++;

                if (child_fd > 0 && sequence == 1) {
                    write(child_fd, "\0", 1);
                }

            } else if (len == 0) {
                if (child_fd > 0) {
                    if (sequence > 1) {
                        write(child_fd, "\0", 1);
                    }
                    close(child_fd);
                }
                child_fd = -1;
            }
        }

        // 主进程已关闭管道写端
        close(exfd[0]);

        // 退出
        _exit(EXIT_SUCCESS);
    } else {
        // 主进程，关闭管道的读端
        close(exfd[0]);
    }
}

void waitExecuter()
{
    // 关闭写端，读取端 EOF
    close(exfd[1]);

    DEBUG(("waiting for executer process (%u) quit ...", expid))
    waitpid(expid, NULL, 0);
    DEBUG(("ok!\n"))
}

